package com.gesture;

import java.util.LinkedList;
import java.util.List;

import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Size;
import org.opencv.highgui.Highgui;
import org.opencv.highgui.VideoCapture;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.alarm.R;


public abstract class CvViewBase extends SurfaceView implements SurfaceHolder.Callback, Runnable {
	private static final String TAG = "CvView";

	private SurfaceHolder mHolder;
	private VideoCapture mCamera;
	protected LinkedList<Integer> gestures;

	public CvViewBase(Context context) {
		super(context);
		mHolder = getHolder();
		mHolder.addCallback(this);
		Log.i(TAG, "Instantiated new " + this.getClass());
	}

	public boolean openCamera() {
		Log.i(TAG, "openCamera");
		synchronized (this) {
			releaseCamera();
			mCamera = new VideoCapture(Highgui.CV_CAP_ANDROID);
			if (!mCamera.isOpened()) {
				mCamera.release();
				mCamera = null;
				Log.e(TAG, "Failed to open native camera");
				return false;
			}
		}

		mCamera.set(Highgui.CV_CAP_PROP_ANDROID_FLASH_MODE, Highgui.CV_CAP_ANDROID_FLASH_MODE_TORCH);
		return true;
	}

	public void releaseCamera() {
		Log.i(TAG, "releaseCamera");
		synchronized (this) {
			if (mCamera != null) {
				mCamera.release();
				mCamera = null;
			}
		}
	}

	public void setupCamera(int width, int height) {
		Log.i(TAG, "setupCamera(" + width + ", " + height + ")");
		synchronized (this) {
			if (mCamera != null && mCamera.isOpened()) {
				List<Size> sizes = mCamera.getSupportedPreviewSizes();
				int mFrameWidth = width;
				int mFrameHeight = height;

				// selecting optimal camera preview size
				{
					double minDiff = Double.MAX_VALUE;
					for (Size size : sizes) {
						if (Math.abs(size.height - height) < minDiff) {
							mFrameWidth = (int) size.width;
							mFrameHeight = (int) size.height;
							minDiff = Math.abs(size.height - height);
						}
					}
				}

				mCamera.set(Highgui.CV_CAP_PROP_FRAME_WIDTH, mFrameWidth);
				mCamera.set(Highgui.CV_CAP_PROP_FRAME_HEIGHT, mFrameHeight);
				
				gap = new Point((getRight() - mFrameWidth) / 2 + handWidth,
						(getBottom() - mFrameHeight) / 2 + handHeight);
				leftMargin = gap.x;
				rightMargin = getRight() - gap.x - handWidth;
				topMargin = gap.y;
				bottomMargin = getBottom() - gap.y - handHeight;
				center=new Point((getWidth() - handWidth) / 2, (getHeight() - handHeight) / 2);
			}
		}

	}

	public void surfaceChanged(SurfaceHolder _holder, int format, int width, int height) {
		Log.i(TAG, "surfaceChanged");
		setupCamera(width, height);
	}

	public void surfaceCreated(SurfaceHolder holder) {
		Log.i(TAG, "surfaceCreated");
		(new Thread(this)).start();
	}

	public void surfaceDestroyed(SurfaceHolder holder) {
		Log.i(TAG, "surfaceDestroyed");
		releaseCamera();
	}

	protected abstract Bitmap processFrame(VideoCapture capture);

	public void run() {
		Log.i(TAG, "Starting processing thread");
		int currGesture = gestures.pop();
		while (true) {
			Bitmap bmp = null;
			synchronized (this) {
				if (mCamera == null) break;
				if (!mCamera.grab()) {
					Log.e(TAG, "mCamera.grab() failed");
					break;
				}

				bmp = processFrame(mCamera);
			}

			if (bmp != null) {
				Canvas canvas = mHolder.lockCanvas();
				if (canvas != null) {
					canvas.drawBitmap(bmp, (canvas.getWidth() - bmp.getWidth()) / 2, (canvas.getHeight() - bmp.getHeight()) / 2, null);
					if (handStartRect == null) 
						initStartEnd(currGesture);
					moveHand(bmp, currGesture);
					if (handStartRect.contains(endPt)) {
						handEnd = HAND_BLUE;
						if (gestures.isEmpty()){
							Log.d(TAG, "Succeed");
							setStatus((byte) 1);
						} else{
							Log.d(TAG, "New Gesture");
							currGesture = gestures.pop();
							initStartEnd(currGesture);
							prevRect = null;
						}
					} else
						handEnd = HAND_GRAY;
					canvas.drawBitmap(handStart, handStartRect.x, handStartRect.y, null);
					canvas.drawBitmap(handEnd, handEndRect.x, handEndRect.y, null);
					mHolder.unlockCanvasAndPost(canvas);
				}
				bmp.recycle();
			}
		}
		Log.i(TAG, "Finishing processing thread");
	}

	private void moveHand(Bitmap bmp) {
		if (handStartRect == null) {
			startPt = new Point(getRight() - ((getRight() - bmp.getWidth()) / 2) - handStart.getWidth(),
					(getHeight() - handStart.getHeight()) / 2);
			handStartRect = new Rect(startPt, new Point(startPt.x + handStart.getWidth(), startPt.y + handStart.getHeight()));
			endPt = new Point(getWidth() - handStartRect.br().x, startPt.y);
			handEndRect = new Rect(new Point(endPt.x, startPt.y), new Point(endPt.x + handEnd.getWidth(), startPt.y + handEnd.getHeight()));
			endPt.x += handEndRect.width / 2;
			endPt.y += handEndRect.height / 2;
		}

		if (currRect != null && currRect.boundingRect().contains(handStartRect.tl())) {
			handStart = HAND_PINK;
			if (prevRect != null) {
				float x = (float) (handStartRect.x + currRect.center.x - prevRect.center.x);
				if (x < startPt.x)
				// if (prevRect.x>currRect.x)
					handStartRect.x = (int) x;
			}

			prevRect = currRect;
		} else {
			handStart = HAND_RED;
			prevRect = null;
		}
	}
	private void moveHand(Bitmap bmp, int direction) {
		if (currRect != null && 
				(currRect.boundingRect().contains(handStartRect.tl()) ||
						currRect.boundingRect().contains(handStartRect.br()))) {
			if (prevRect != null){
				switch(direction){
				case moveTo.LEFT:
				case moveTo.RIGHT:
					int x = (int) (currRect.center.x-prevRect.center.x);
					if (Math.abs(x)<=2*handWidth)
						handStartRect.x += x;
					if(handStartRect.x>rightMargin) handStartRect.x = (int) (rightMargin);
					if(handStartRect.x<leftMargin) handStartRect.x = (int) (leftMargin);
					break;
				case moveTo.TOP:
				case moveTo.BOTTOM:
					if(Math.abs(currRect.boundingRect().y - prevRect.boundingRect().y)<=2*handHeight)
						handStartRect.y += (int) (currRect.center.y-prevRect.center.y);
					if(handStartRect.y>bottomMargin) handStartRect.y = (int) (bottomMargin);
					if(handStartRect.y<topMargin) handStartRect.y = (int) (topMargin);
					break;
				}
			}
			handStart = HAND_PINK;
			prevRect = currRect;
		} else {
			handStart = HAND_RED;
			prevRect = null;
		}
	}

	private void initStartEnd(int direction) {
		Log.d(TAG, "Init Gesture");
		switch (direction){
		case moveTo.LEFT: 
			startPt = new Point(rightMargin, bottomMargin);
			endPt = new Point(leftMargin, bottomMargin);
			break;
		case moveTo.RIGHT: 
			startPt = new Point(leftMargin, bottomMargin);
			endPt = new Point(rightMargin, bottomMargin);
			break;
		case moveTo.TOP:
			startPt = new Point(rightMargin, bottomMargin);
			endPt = new Point(rightMargin, topMargin);
			break;
		case moveTo.BOTTOM:
			startPt = new Point(rightMargin, topMargin);
			endPt = new Point(rightMargin, bottomMargin);
			break;
		}
		handStartRect = new Rect(startPt, new Point(startPt.x + handWidth, startPt.y + handHeight));
		handEndRect = new Rect(endPt, new Point(endPt.x + handWidth, endPt.y + handHeight));
		endPt.x += handWidth / 2;
		endPt.y += handHeight / 2;
	}

	private synchronized void setStatus(byte s){
		status = s;
	}

	public synchronized byte getStatus(){
		return status;
	}
	
	interface moveTo { int LEFT=1, RIGHT=2, TOP=3, BOTTOM=4;}
	
	Point gap ;
	double leftMargin, rightMargin, topMargin, bottomMargin;
	private final Bitmap HAND_PINK = BitmapFactory.decodeResource(getResources(), R.drawable.hand_pink), HAND_GRAY = BitmapFactory.decodeResource(
			getResources(), R.drawable.hand_gray), HAND_BLUE = BitmapFactory.decodeResource(getResources(), R.drawable.hand_blue),
			HAND_ORANGE = BitmapFactory.decodeResource(getResources(), R.drawable.hand_orange), HAND_RED = BitmapFactory.decodeResource(
					getResources(), R.drawable.hand_red);
	Bitmap handStart = HAND_RED, handEnd = HAND_GRAY;
	private final double handWidth = handStart.getWidth(),
			handHeight = handStart.getHeight();
	Rect handStartRect, handPrevRect, handEndRect;
	Point startPt, endPt, centerPt, center;
	protected RotatedRect currRect, prevRect;
	protected byte status = 0;

}